package com.ideal.frame.jpa;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPADeleteClause;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.querydsl.jpa.impl.JPAUpdateClause;
import com.ideal.frame.util.BeanMapper;
import org.hibernate.SQLQuery;
import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.QuerydslJpaRepository;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.util.CollectionUtils;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class JpaRepositoryImpl<T, ID extends Serializable>
        extends QuerydslJpaRepository<T, ID> implements JpaRepository<T, ID> {
    protected Logger LOGGER;
    private final JpaEntityInformation<T, ID> entityInformation;
    private final EntityManager em;
    private final EntityPath<T> path;
    private final JPAQueryFactory queryFactory;

    public JpaRepositoryImpl(JpaEntityInformation<T,ID> entityInformation, EntityManager entityManager) {
        this(entityInformation, entityManager, SimpleEntityPathResolver.INSTANCE);
    }

    public JpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, EntityPathResolver resolver) {
        super(entityInformation, entityManager, resolver);
        this.LOGGER = LoggerFactory.getLogger(JpaRepositoryImpl.class);
        this.entityInformation = entityInformation;
        this.em = entityManager;
        this.path = resolver.createPath(entityInformation.getJavaType());
        this.queryFactory = new JPAQueryFactory(entityManager);
    }

    public JpaRepositoryImpl(Class<T> domainClass, EntityManager em) {
        this((JpaEntityInformation<T, ID>)JpaEntityInformationSupport.getEntityInformation(domainClass,em), em);
    }

    public T findOne(BooleanBuilder builder){
        return this.createQuery(builder).select(this.path).fetchOne();
    }

    public int deleteInId(Collection<ID> ids) {
        if (ids != null && !ids.isEmpty()) {
            String ql = QueryUtils.getQueryString("delete from %s x", this.entityInformation.getEntityName());
            this.LOGGER.info(ql);
            return this.applyAndBind(ql, ids, this.em).executeUpdate();
        } else {
            return 0;
        }
    }

    public Query applyAndBind(String queryString, Collection<ID> ids, EntityManager entityManager) {
        String alias = QueryUtils.detectAlias(queryString);
        String sb = queryString + " where " + String.format(" %s.id IN(?1)", alias);
        Query query = entityManager.createQuery(sb);
        query.setParameter(1, ids);
        return query;
    }

    public Page<T> findAll(DSLPageRequest pageRequest) {
        JPQLQuery countQuery = this.createQuery(new Predicate[]{pageRequest.toPredicate()});
        JPQLQuery query = this.createQuery(new Predicate[]{pageRequest.toPredicate()});
        query.offset(pageRequest.getOffset());
        query.limit(pageRequest.getPageSize());
        Long total = countQuery.fetchCount();
        query.orderBy(pageRequest.toOrderSpecifier(this.path));
        List<T> content = total.longValue() > pageRequest.getOffset() ? query.fetchAll().fetch() : Collections.emptyList();
        return new PageImpl(content, PageRequest.of(pageRequest.getPageNumber(), pageRequest.getPageSize()), total.longValue());
    }

    public <RT> Page<RT> findAll(DSLPageRequest pageRequest, Expression<RT> listExpr) {
        JPQLQuery countQuery = this.createQuery(new Predicate[]{pageRequest.toPredicate()});
        JPQLQuery query = this.createQuery(new Predicate[]{pageRequest.toPredicate()});
        query.offset(pageRequest.getOffset());
        query.limit(pageRequest.getPageSize());
        Long total = countQuery.fetchCount();
        query.orderBy(pageRequest.toOrderSpecifier(this.path));
        List<RT> content = total.longValue() > pageRequest.getOffset() ? query.select(listExpr).fetchAll().fetch() : Collections.emptyList();
        return new PageImpl(content, PageRequest.of(pageRequest.getPageNumber(), pageRequest.getPageSize()), total.longValue());
    }

    public List<T> findAll(DSLRequest request) {
        JPQLQuery query = this.createQuery(new Predicate[]{request.toPredicate()});
        query.orderBy(request.toOrderSpecifier(this.path));
        List<T> content = query.fetchAll().fetch();
        return content;
    }

    public List<T> findAll(OrderSpecifier... orderSpecifiers) {
        JPQLQuery query = this.createQuery(new Predicate[0]);
        query.orderBy(orderSpecifiers);
        return query.fetchAll().fetch();
    }

    public <T> Page<T> selectByNativeQuery(String sql, List<Object> params, Class<T> cls, Pageable pageable) {
        Query query= em.createNativeQuery(sql);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        if (params != null && !params.isEmpty()) {
            for (int i=0; i < params.size(); i++) {
                query.setParameter(i + 1, params.get(i));
            }
        }
        int total = getCountByNativeQuery(sql,params);
        query.setMaxResults(pageable.getPageSize());
        query.setFirstResult(pageable.getPageNumber()*pageable.getPageSize());
        List<Map> content = query.getResultList();
        return new PageImpl<T>(BeanMapper.mapList(content, cls),pageable,total);
    }

    public <T> List<T> selectByNativeQuery(String sql, List<Object> params, Class<T> cls){
        Query query= em.createNativeQuery(sql);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        if (params != null && !params.isEmpty()) {
            for (int i=0; i < params.size(); i++) {
                query.setParameter(i + 1, params.get(i));
            }
        }
        List<Map> content = query.getResultList();
        return BeanMapper.mapList(content, cls);
    }

    public int getCountByNativeQuery(String sql, List<Object> params) {
        sql = String.format("select count(1) from (%s) finishTab",sql);
        Query query = em.createNativeQuery(sql);
        if (params != null && !params.isEmpty()) {
            for (int i=0; i < params.size(); i++) {
                query.setParameter(i + 1, params.get(i));
            }
        }
        return Integer.parseInt(query.getSingleResult().toString());
    }

    public <T> List<T> selectByHQL(String hql, List<Object> params, Class<T> cls){
        Query query= em.createQuery(hql,cls);
        if (!CollectionUtils.isEmpty(params)) {
            for (int i=0; i < params.size(); i++) {
                query.setParameter(i + 1, params.get(i));
            }
        }
        return query.getResultList();
    }

    public JPAQueryFactory queryFactory() {
        return this.queryFactory;
    }

    public JPADeleteClause delete(EntityPath<?> path) {
        return this.queryFactory.delete(path);
    }

    public <T> JPAQuery<T> select(Expression<T> expr) {
        return this.queryFactory.select(expr);
    }

    public JPAQuery<Tuple> select(Expression<?>[] exprs) {
        return this.queryFactory.select(exprs);
    }

    public <T> JPAQuery<T> selectDistinct(Expression<T> expr) {
        return this.queryFactory.selectDistinct(expr);
    }

    public JPAQuery<Tuple> selectDistinct(Expression<?>[] exprs) {
        return this.queryFactory.selectDistinct(exprs);
    }

    public JPAQuery<Integer> selectOne() {
        return this.queryFactory.selectOne();
    }

    public JPAQuery<Integer> selectZero() {
        return this.queryFactory.selectZero();
    }

    public <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
        return this.queryFactory.selectFrom(from);
    }

    public JPAQuery<?> from(EntityPath<?> from) {
        return this.queryFactory.from(from);
    }

    public JPAQuery<?> from(EntityPath<?>[] from) {
        return this.queryFactory.from(from);
    }

    public JPAUpdateClause update(EntityPath<?> path) {
        return this.queryFactory.update(path);
    }

    public JPAQuery<?> query() {
        return this.queryFactory.query();
    }

    public JPAUpdateClause updateClause() {
        return new JPAUpdateClause(this.em, this.path);
    }

    public JPADeleteClause deleteClause() {
        return new JPADeleteClause(this.em, this.path);
    }
}

